<?php
/**
 * Parcl
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://parcl.com/license
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@parcl.com so we can send you a copy immediately.
 *
 * @category parcl
 * @package data-store
 * @subpackage database
 * @copyright Copyright (c) 2006-2011 Mikael Knutsson (http://mikn.me)
 * @license http://parcl.com/license New BSD License
 */

if(PARCL_EXEC !== 1) {
    die('<h1>Incorrect Access</h1>You cannot access this file directly.<br/>' .
         'If you got this error message by clicking a link on the site,' .
         'please notify the webmaster as soon as possible.');
}

abstract class Query extends Events {
    protected $finalQuery = null;
    private $checks = null;

    abstract public function GetQuery();
    abstract protected function eventHandlers();

    protected function __construct() {
        parent::__construct();
    }

    final private function setChecks($tableAlias) {
        $registry = Registry::GetInstance();
        if(!is_null($tableAlias)) {
            $tableAlias = trim($tableAlias) . '.';
        }
        $tA = $tableAlias;
        $checks = ' (' . $tA . '`published` < ' . $registry->Get('currentTime') .
                  ' AND ' . $tA . '`enabled` = 1 AND ' . $tA . '`deleted` = 0) ';
        $this->checks = $checks;
        return true;
    }

    final private function getChecks() {
        return $this->checks;
    }

    final private function addChecks(&$query, array $operands) {
        foreach($operands as $operand) {
            $place = mb_strpos($query, mb_strtoupper($operand));
            if($place !== false) {
                $operators = mb_substr($query, $place);
                $query = mb_substr($query, 0, $place);
                $operators = ' WHERE ' . $this->getChecks() . $operators;
                return $operators;
            }
        }
        return $operators = ' WHERE ' . $this->getChecks();
    }

    final public function ViewCheck(parclDB $sender, $args) {
        $query = &$args['query'];
        if($args['check'] === false) {
            return false;
        }
        $check = (is_string($args['check']) ? $args['check'] : null);
        $where = mb_strpos($query, 'WHERE');
        $operands = array('ORDER BY', 'LIMIT');
        $this->setChecks($check);

        if($where !== false) {
            $operators = mb_substr($query, $where + 5);
            $query = mb_substr($query, 0, $where);
            $operators = ' WHERE ' . $this->getChecks() . ' AND ' . $operators;
        }
        else {
            $operators = $this->addChecks($query, $operands);
        }
        $query = $query . $operators;
        return true;
    }

    final public function SetParams() {
        $args = func_get_args();
        $acceptedArgs = array('boolean' => PDO::PARAM_BOOL, 'double' => PDO::PARAM_STR,
                               'integer' => PDO::PARAM_INT, 'string' => PDO::PARAM_STR,
                               'array' => PDO::PARAM_STR, 'NULL' => PDO::PARAM_NULL,
                               'LOB' => PDO::PARAM_LOB);

        foreach($args as $k => $arg) {
            $type = gettype($arg);
            if(!array_key_exists($type, $acceptedArgs)) {
                throw new DBException('Unsupported parameter type.', DBException::PARAM);
            }

            $value = ($type == 'array' ? json_encode($arg) : $arg);
            $params[$k] = array('value' => $value, 'type' => $acceptedArgs[$type]);
            if($params[$k]['type'] === $acceptedArgs['string']) {
                $length = mb_strlen($params[$k]['value']);
                if($length > parclDB::BLKSIZE) {
                    $params[$k]['type'] = $acceptedArgs['LOB'];
                    $params[$k]['value'] = $this->fetchStream($value);
                }
            }
        }
        $this->triggerEvent('DB:PARAMS_SET', array('params' => $params));
        return true;
    }

    final private function fetchStream($value) {
        return stringStream::open($value);
    }

    final public function MarkerCount() {
        return mb_substr_count($this->finalQuery, '?');
    }

    final protected function genEventHandlers() {
        $registry = Registry::GetInstance();
        $DB = $registry->Get('DB');
        $this->AddEventHandler('DB:QUERY_RECEIVED', array($this, 'viewCheck'));
        $this->AddEventHandler('DB:PARAMS_SET', array($DB, 'BindParams'));
        return true;
    }
    public function FetchResult() {
        $output = true;
        $this->triggerEvent('DB:FETCH_RESULT', array('output' => &$output));
        $this->resetNamespace('DB');
        return $output;
    }
}

final class simpleSelect extends Query {
    private $tables = array();
    private $opers = array();

    public function __construct(array $tables, $opers = array()) {
        parent::__construct();
        $this->tables = $tables;
        $this->opers = $opers;
        $this->finalQuery = $this->prepareQuery();
        $this->eventHandlers();
    }

    private function prepareQuery() {
        $operators = null;
        $tables = $this->tables;
        $opers = $this->opers;

        foreach($tables as $tabname => $cols) {
            foreach($cols as $col) {
                $column[] = $tabname . '.' . $col;
            }
            $table[] = $tabname;
            $numCols[] = count($cols);
        }
        $select = implode(', ', $column);
        $from = implode(', ', $table);
        $this->numCols = array_sum($numCols);

        if(!empty($opers)) {
            $operation = null;
            foreach($opers as $operand => $operations) {
                if(mb_strtolower($operand) === 'where') {
                    $operators .= ' ' . mb_strtoupper($operand) . ' (';
                    foreach($operations as $oper => $comp) {
                        if(!is_int($oper)) {
                            $operation .= ' ' . mb_strtoupper($oper);
                        }
                        $operation .= ' ' . $comp;
                    }
                    $operators .= $operation . ') ';
                }
                else {
                    $operators .= ' ' . mb_strtoupper($operand) . ' ' . $operations;
                }
            }
        }
        unset($finalQuery);
        $finalQuery = 'SELECT ' . $select . ' FROM ' . $from . $operators;
        return $finalQuery;
    }

    public function GetQuery() {
        return $this->finalQuery;
    }

    protected function eventHandlers() {
        $registry = Registry::GetInstance();
        $DB = $registry->Get('DB');

        $this->genEventHandlers();
        $this->AddEventHandler('DB:PARAMS_BOUND', array($DB, 'Execute'));
        $this->AddEventHandler('DB:FETCH_RESULT', array($DB, 'fetchResult'));
        $this->AddEventHandler('DB:RESULT_GENERATED', array($DB, 'CloseCursor'));
        return true;
    }
}

final class pQuery extends Query {
    public function __construct($query) {
        parent::__construct();
        $this->finalQuery = $query;
        $this->eventHandlers();
    }

    public function GetQuery() {
        return $this->finalQuery;
    }

    protected function eventHandlers() {
        $registry = Registry::GetInstance();
        $DB = $registry->Get('DB');

        $this->genEventHandlers();
        $this->AddEventHandler('DB:PARAMS_BOUND', array($DB, 'Execute'));
        $this->AddEventHandler('DB:FETCH_RESULT', array($DB, 'fetchResult'));
        $this->AddEventHandler('DB:RESULT_GENERATED', array($DB, 'CloseCursor'));
        return true;
    }
}